package com.googlecode.connectlet.crypto.rsa.provider;

import java.math.BigInteger;
import java.security.interfaces.RSAPrivateCrtKey;

import com.googlecode.connectlet.crypto.random.RC4PrngRandom;

public final class RSAPrivateCrtKeyImpl implements RSAPrivateCrtKey {
	private static final long serialVersionUID = 1L;

	private static final BigInteger X509_PUBLIC_EXPONENT = BigInteger.valueOf(65537);

	private static RC4PrngRandom random = new RC4PrngRandom();

	private BigInteger p, q, dP, dQ, qInv;

	public RSAPrivateCrtKeyImpl() {
		this(2048);
	}

	public RSAPrivateCrtKeyImpl(int keySize) {
		super();
		BigInteger p1, q1, f;
		do {
			p = BigInteger.probablePrime(keySize / 2, random);
			do {
				q = BigInteger.probablePrime(keySize / 2, random);
				if (p.compareTo(q) < 0) {
					BigInteger t = p;
					p = q;
					q = t;
				}
			} while (p.multiply(q).bitLength() < keySize);
			p1 = p.subtract(BigInteger.ONE);
			q1 = q.subtract(BigInteger.ONE);
			f = p1.multiply(q1);
		} while (!X509_PUBLIC_EXPONENT.gcd(f).equals(BigInteger.ONE));
		BigInteger d = X509_PUBLIC_EXPONENT.modInverse(f);
		dP = d.mod(p1);
		dQ = d.mod(q1);
		qInv = q.modInverse(p);
	}

	public RSAPrivateCrtKeyImpl(BigInteger p, BigInteger q,
			BigInteger dP, BigInteger dQ, BigInteger qInv) {
		this.p = p;
		this.q = q;
		this.dP = dP;
		this.dQ = dQ;
		this.qInv = qInv;
	}

	public BigInteger rsadp(BigInteger data) {
		BigInteger m1 = data.modPow(dP, p);
		BigInteger m2 = data.modPow(dQ, q);
		BigInteger h = m1.subtract(m2).multiply(qInv).mod(p);
		BigInteger m = q.multiply(h).add(m2);
		return m;
	}

	public RSAPublicKeyImpl getPublic() {
		return new RSAPublicKeyImpl(getModulus());
	}

	@Override
	public byte[] getEncoded() {
		return new CompactKeySpec(this).getEncoded();
	}

	@Override
	public BigInteger getPrimeP() {
		return p;
	}

	@Override
	public BigInteger getPrimeQ() {
		return q;
	}

	@Override
	public BigInteger getPrimeExponentP() {
		return dP;
	}

	@Override
	public BigInteger getPrimeExponentQ() {
		return dQ;
	}

	@Override
	public BigInteger getCrtCoefficient() {
		return qInv;
	}

	@Override
	public BigInteger getModulus() {
		return p.multiply(q);
	}

	@Override
	public BigInteger getPublicExponent() {
		return X509_PUBLIC_EXPONENT;
	}

	@Override
	public BigInteger getPrivateExponent() {
		return X509_PUBLIC_EXPONENT.modInverse(p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE)));
	}

	@Override
	public String getAlgorithm() {
		return "RSA";
	}

	@Override
	public String getFormat() {
		return "Compact";
	}
}